package org.jflame.web.filter;

import org.jflame.commons.config.ConfigHolder;
import org.jflame.commons.config.ConfigReader;
import org.jflame.commons.util.ArrayHelper;
import org.jflame.commons.util.StringHelper;
import org.jflame.commons.util.UrlMatcher;
import org.jflame.web.FilterParamConfig;
import org.jflame.web.WebUtils;

import java.io.IOException;

import org.apache.commons.lang3.ArrayUtils;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * 跨域访问处理过滤器
 * <p>
 * 可配置Filter属性:
 * 
 * <pre>
 *  allow-origins
 *  add-headers 多个以逗号分隔
 *  allow-paths 
 *  disallow-paths
 * </pre>
 * 
 * 在默认Properties文件配置,属性名: jflame.web.cors.[filter属性名] (如:allow-origins --&gt; jflame.web.cors.allow-origins)
 * 
 * @author charles.zhang
 */
public class CorsFilter extends OncePerRequestFilter {

    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
    public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
    // private static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
    // private static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
    // private static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
    public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
    public static final String ORIGIN = "Origin";

    private CorsProperties cfg;

    @Override
    protected void doFilterInternal(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws ServletException, IOException {
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpServletRequest request = (HttpServletRequest) req;
        String origin = request.getHeader(ORIGIN);

        if (origin != null) {
            if ("*".equals(cfg.getAllowOrigins()[0]) || ArrayUtils.contains(cfg.getAllowOrigins(), origin)) {
                String requestUrl = WebUtils.getRequestPath(request);
                // 是否是禁止访问的地址
                if (isDisallowUrl(requestUrl)) {
                    return;
                }

                response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
                response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, cfg.getAllowHeaders());
                if (!isPreFlightRequest(request)) {
                    /*if (exposeHeaders != null) {
                        response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
                    }
                    if (maxAge != null)
                     response.setHeader(ACCESS_CONTROL_MAX_AGE, maxAge.toString()); // 设置过期时间
                     **/
                    response.setHeader(ACCESS_CONTROL_ALLOW_METHODS, cfg.getAllowMethods());
                } else {
                    response.getWriter()
                            .flush();
                    return;
                }
            }
        }

        chain.doFilter(request, response);
    }

    /**
     * 是否禁止跨域访问的地址
     * 
     * @param requestUrl
     * @return
     */
    private boolean isDisallowUrl(String requestUrl) {
        // 先判断是否属于禁止的
        if (ArrayUtils.isNotEmpty(cfg.getDisallowPaths())) {
            if (UrlMatcher.match(cfg.getDisallowPaths(), requestUrl)) {
                if (log.isDebugEnabled()) {
                    log.debug("cors disallowed,url:{}", requestUrl);
                }
                return true;
            }
        }
        // 再判断是否属于允许范围的
        if (ArrayUtils.isNotEmpty(cfg.getAllowPaths())) {
            if (!UrlMatcher.match(cfg.getAllowPaths(), requestUrl)) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected void internalInit(FilterConfig filterConfig) {
        cfg = new CorsProperties();
        boolean hasVal = new FilterParamConfig(filterConfig).bindBean(cfg, null);
        if (!hasVal && ConfigHolder.getConfig() != null) {
            ConfigHolder.getConfig()
                    .bindBean(cfg, CorsProperties.PREFIX);
        }
    }

    /**
     * Returns {@code true} if the request is a valid CORS one.
     */
    public static boolean isCorsRequest(HttpServletRequest request) {
        return (request.getHeader(ORIGIN) != null);
    }

    /**
     * Returns {@code true} if the request is a valid CORS pre-flight one.
     */
    public static boolean isPreFlightRequest(HttpServletRequest request) {
        return (isCorsRequest(request) && "OPTIONS".equalsIgnoreCase(request.getMethod())
                && request.getHeader(ACCESS_CONTROL_REQUEST_METHOD) != null);
    }

    @Override
    public void destroy() {

    }

    public static class CorsProperties {

        public static final String PREFIX = ConfigReader.JFLAME_CFG_KEY_PREFIX + ".web.cors.";

        private String[] allowOrigins = ArrayHelper.of("*");
        private String allowMethods = "POST,GET,HEAD,OPTIONS";
        private String allowHeaders = "Origin,X-Requested-With,Content-Type,Accept";
        private String[] allowPaths;// 允许跨域访问的接口地址
        private String[] disallowPaths;// 禁止跨域访问的接口地址
        // private Long maxAge;
        // private String allowCredentials;
        // private String exposeHeaders;

        public String[] getAllowOrigins() {
            return allowOrigins;
        }

        public void setAllowOrigins(String[] allowOrigins) {
            if (ArrayHelper.isNotEmpty(allowOrigins)) {
                this.allowOrigins = allowOrigins;
            }
        }

        public String getAllowMethods() {
            return allowMethods;
        }

        public void setAllowMethods(String _allowMethods) {
            if (StringHelper.isNotEmpty(_allowMethods)) {
                this.allowMethods = _allowMethods;
            }
        }

        public String getAllowHeaders() {
            return allowHeaders;
        }

        public void setAllowHeaders(String _allowHeaders) {
            if (StringHelper.isNotEmpty(_allowHeaders)) {
                allowHeaders = allowHeaders + "," + _allowHeaders;
            }
        }

        public String[] getAllowPaths() {
            return allowPaths;
        }

        public void setAllowPaths(String[] allowPaths) {
            this.allowPaths = allowPaths;
        }

        public String[] getDisallowPaths() {
            return disallowPaths;
        }

        public void setDisallowPaths(String[] disallowPaths) {
            this.disallowPaths = disallowPaths;
        }

    }
}
